YouTubeChatIframe.tsx 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. 'use client';
  2. import { useEffect, useState } from 'react';
  3. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
  4. import { faCoins } from '@fortawesome/free-solid-svg-icons';
  5. import './youtube-chat-iframe.scss';
  6. type Props = {
  7. videoId: string|null;
  8. onDonate?: () => void;
  9. };
  10. /**
  11. * YouTube Live Chat 임베드 컴포넌트.
  12. *
  13. * - URL 형식: https://www.youtube.com/live_chat?v={VIDEO_ID}&embed_domain={HOST}
  14. * - embed_domain 은 NEXT_PUBLIC_EMBED_DOMAIN 환경변수 사용 (호스트만, 프로토콜/포트 제외)
  15. * - 라이브 중일 때만 정상 표시 (videoId 가 있어야 함)
  16. * - 비라이브 또는 환경변수 미설정 시 fallback UI
  17. *
  18. * Quota 비용: 0 — YouTube 자체 인프라 사용, dpot 의 quota 소비 없음.
  19. */
  20. export default function YouTubeChatIframe({ videoId, onDonate }: Props)
  21. {
  22. const [embedDomain, setEmbedDomain] = useState<string|null>(null);
  23. useEffect(() => {
  24. // SSR 시점에는 process.env 가 없을 수 있어 마운트 후 해석
  25. const envDomain = process.env.NEXT_PUBLIC_EMBED_DOMAIN;
  26. if (envDomain) {
  27. setEmbedDomain(envDomain);
  28. return;
  29. }
  30. // fallback: 현재 호스트 (개발 환경 보조)
  31. if (typeof window !== 'undefined') {
  32. setEmbedDomain(window.location.hostname);
  33. }
  34. }, []);
  35. const chatUrl = videoId && embedDomain
  36. ? `https://www.youtube.com/live_chat?v=${encodeURIComponent(videoId)}&embed_domain=${encodeURIComponent(embedDomain)}`
  37. : null;
  38. return (
  39. <div className="yt-chat">
  40. {chatUrl ? (
  41. <iframe
  42. className="yt-chat__iframe"
  43. src={chatUrl}
  44. title="YouTube Live Chat"
  45. allow="autoplay"
  46. sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms"
  47. />
  48. ) : (
  49. <div className="yt-chat__offline">
  50. <p className="yt-chat__offline-title">현재 라이브 방송이 아닙니다</p>
  51. <p className="yt-chat__offline-desc">라이브 방송이 시작되면 채팅에 참여할 수 있어요.</p>
  52. </div>
  53. )}
  54. {onDonate && (
  55. <div className="yt-chat__footer">
  56. <button
  57. type="button"
  58. className="yt-chat__donate-btn"
  59. onClick={onDonate}
  60. aria-label="후원하기"
  61. >
  62. <FontAwesomeIcon icon={faCoins} />
  63. <span>후원하기</span>
  64. </button>
  65. </div>
  66. )}
  67. </div>
  68. );
  69. }